package org.wheel.quartz;

import org.quartz.JobExecutionContext;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.util.ReflectionUtils;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Stream;

/**
 * @author klaus
 * @date 2021/6/30 华小勇 15:41
 */
public class DynamicQuartzJobBean extends QuartzJobBean {

    private BeanFactory beanFactory;

    private static final Map<String, JobExecutor> methodInvoker = new ConcurrentHashMap<>();

    @Override
    public void executeInternal(JobExecutionContext context) {

        JobExecutor jobExecutor = methodInvoker.computeIfAbsent(context.getJobDetail().getKey().toString()
                , (k) -> this.buildJobExecutor(context.getJobDetail().getJobDataMap().getString("bean")
                        , context.getJobDetail().getJobDataMap().getString("method")));

        Optional.ofNullable(jobExecutor).ifPresent((job) -> job.execute(context));
    }

    protected JobExecutor buildJobExecutor(String bean, String methodName) {
        Class<?> type = beanFactory.getType(bean);
        if (type == null) {
            return null;
        }
        Optional<Method> optional = Stream.of(ReflectionUtils.getDeclaredMethods(type))
                .filter(e -> e.getName().equals(methodName)).findFirst();
        if (!optional.isPresent()) {
            return null;
        }
        Method method = optional.get();
        ReflectionUtils.makeAccessible(method);
        Class<?>[] parameterTypes = method.getParameterTypes();
        Function<JobExecutionContext, Object[]> args = (ctx) -> {
            Object[] objects = new Object[parameterTypes.length];
            for (int i = 0; i < parameterTypes.length; i++) {
                objects[i] = parameterTypes[i] == JobExecutionContext.class
                        ? ctx : beanFactory.getBean(parameterTypes[i]);
            }
            return objects;
        };
        return new JobExecutor(method, beanFactory.getBean(type), args);
    }

    @Resource
    public void setBeanFactory(BeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }

}
